﻿namespace Edge.Utilities.Imaging
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Specifies the data type of the values stored in the value data member of that same PropertyItem object.
    /// </summary>
    public enum PropertyTagType : short
    {
        /// <summary>Specifies that the format is 4 bits per pixel, indexed.</summary>
        PixelFormat4bppIndexed = 0,

        /// <summary>Specifies that the value data member is an array of bytes.</summary>
        Byte = 1,

        /// <summary>Specifies that the value data member is a null-terminated ASCII string. If you set the type data member of a PropertyItem object to PropertyTagTypeASCII, you should set the length data member to the length of the string including the NULL terminator. For example, the string HELLO would have a length of 6.</summary>
        ASCII = 2,

        /// <summary>Specifies that the value data member is an array of unsigned short (16-bit) integers.</summary>
        Int16 = 3,

        /// <summary>Specifies that the value data member is an array of unsigned long (32-bit) integers.</summary>
        Int32 = 4,

        /// <summary>Specifies that the value data member is an array of pairs of unsigned long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.</summary>
        Rational = 5,

        /// <summary>Specifies that the value data member is an array of bytes that can hold values of any data type.</summary>
        Undefined = 7,

        /// <summary>Specifies that the value data member is an array of signed long (32-bit) integers.</summary>
        SLONG = 9,

        /// <summary>Specifies that the value data member is an array of pairs of signed long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.</summary>
        SRational = 10
    }

    /// <summary>
    /// The following Enumeration gives list (and descriptions) of the property items supported in EXIF format.
    /// </summary>
    public enum PropertyTagId : int
    {
        Artist = 0x013B,

        BitsPerSample = 0x0102,

        CellHeight = 0x0109,

        CellWidth = 0x0108,

        ChrominanceTable = 0x5091,

        ColorMap = 0x0140,

        ColorTransferFunction = 0x501A,

        Compression = 0x0103,

        Copyright = 0x8298,

        DateTime = 0x0132,

        DocumentName = 0x010D,

        DotRange = 0x0150,

        Camera_Make = 0x010F,

        Camera_Model = 0x0110,

        ExifAperture = 0x9202,

        ExifBrightness = 0x9203,

        ExifCfaPattern = 0xA302,

        ExifColorSpace = 0xA001,

        ExifCompBPP = 0x9102,

        ExifCompConfig = 0x9101,

        ExifDTDigitized = 0x9004,

        ExifDTDigSS = 0x9292,

        ExifDTOrig = 0x9003,

        ExifDTOrigSS = 0x9291,

        ExifDTSubsec = 0x9290,

        ExifExposureBias = 0x9204,

        ExifExposureIndex = 0xA215,

        ExifExposureProg = 0x8822,

        ExifExposureTime = 0x829A,

        ExifFileSource = 0xA300,

        ExifFlash = 0x9209,

        ExifFlashEnergy = 0xA20B,

        ExifFNumber = 0x829D,

        ExifFocalLength = 0x920A,

        ExifFocalResUnit = 0xA210,

        ExifFocalXRes = 0xA20E,

        ExifFocalYRes = 0xA20F,

        ExifFPXVer = 0xA000,

        ExifIFD = 0x8769,

        ExifInterop = 0xA005,

        ExifISOSpeed = 0x8827,

        ExifLightSource = 0x9208,

        ExifMakerNote = 0x927C,

        ExifMaxAperture = 0x9205,

        ExifMeteringMode = 0x9207,

        ExifOECF = 0x8828,

        ExifPixXDim = 0xA002,

        ExifPixYDim = 0xA003,

        ExifRelatedWav = 0xA004,

        ExifSceneType = 0xA301,

        ExifSensingMethod = 0xA217,

        ExifShutterSpeed = 0x9201,

        ExifSpatialFR = 0xA20C,

        ExifSpectralSense = 0x8824,

        ExifSubjectDist = 0x9206,

        ExifSubjectLoc = 0xA214,

        ExifUserComment = 0x9286,

        ExifVer = 0x9000,

        ExtraSamples = 0x0152,

        FillOrder = 0x010A,

        FrameDelay = 0x5100,

        FreeByteCounts = 0x0121,

        FreeOffset = 0x0120,

        Gamma = 0x0301,

        GlobalPalette = 0x5102,

        GpsAltitude = 0x0006,

        GpsAltitudeRef = 0x0005,

        GpsDestBear = 0x0018,

        GpsDestBearRef = 0x0017,

        GpsDestDist = 0x001A,

        GpsDestDistRef = 0x0019,

        GpsDestLat = 0x0014,

        GpsDestLatRef = 0x0013,

        GpsDestLong = 0x0016,

        GpsDestLongRef = 0x0015,

        GpsGpsDop = 0x000B,

        GpsGpsMeasureMode = 0x000A,

        GpsGpsSatellites = 0x0008,

        GpsGpsStatus = 0x0009,

        GpsGpsTime = 0x0007,

        GpsIFD = 0x8825,

        GpsImgDir = 0x0011,

        GpsImgDirRef = 0x0010,

        GpsLatitude = 0x0002,

        GpsLatitudeRef = 0x0001,

        GpsLongitude = 0x0004,

        GpsLongitudeRef = 0x0003,

        GpsMapDatum = 0x0012,

        GpsSpeed = 0x000D,

        GpsSpeedRef = 0x000C,

        GpsTrack = 0x000F,

        GpsTrackRef = 0x000E,

        GpsVer = 0x0000,

        GrayResponseCurve = 0x0123,

        GrayResponseUnit = 0x0122,

        GridSize = 0x5011,

        HalftoneDegree = 0x500C,

        HalftoneHints = 0x0141,

        HalftoneLPI = 0x500A,

        HalftoneLPIUnit = 0x500B,

        HalftoneMisc = 0x500E,

        HalftoneScreen = 0x500F,

        HalftoneShape = 0x500D,

        HostComputer = 0x013C,

        ICCProfile = 0x8773,

        ICCProfileDescriptor = 0x0302,

        ImageDescription = 0x010E,

        ImageHeight = 0x0101,

        ImageTitle = 0x0320,

        ImageWidth = 0x0100,

        IndexBackground = 0x5103,

        IndexTransparent = 0x5104,

        InkNames = 0x014D,

        InkSet = 0x014C,

        JPEGACTables = 0x0209,

        JPEGDCTables = 0x0208,

        JPEGInterFormat = 0x0201,

        JPEGInterLength = 0x0202,

        JPEGLosslessPredictors = 0x0205,

        JPEGPointTransforms = 0x0206,

        JPEGProc = 0x0200,

        JPEGQTables = 0x0207,

        JPEGQuality = 0x5010,

        JPEGRestartInterval = 0x0203,

        LoopCount = 0x5101,

        LuminanceTable = 0x5090,

        MaxSampleValue = 0x0119,

        MinSampleValue = 0x0118,

        NewSubfileType = 0x00FE,

        NumberOfInks = 0x014E,

        Orientation = 0x0112,

        PageName = 0x011D,

        PageNumber = 0x0129,

        PaletteHistogram = 0x5113,

        PhotometricInterp = 0x0106,

        PixelPerUnitX = 0x5111,

        PixelPerUnitY = 0x5112,

        PixelUnit = 0x5110,

        PlanarConfig = 0x011C,

        Predictor = 0x013D,

        PrimaryChromaticities = 0x013F,

        PrintFlags = 0x5005,

        PrintFlagsBleedWidth = 0x5008,

        PrintFlagsBleedWidthScale = 0x5009,

        PrintFlagsCrop = 0x5007,

        PrintFlagsVersion = 0x5006,

        REFBlackWhite = 0x0214,

        ResolutionUnit = 0x0128,

        ResolutionXLengthUnit = 0x5003,

        ResolutionXUnit = 0x5001,

        ResolutionYLengthUnit = 0x5004,

        ResolutionYUnit = 0x5002,

        RowsPerStrip = 0x0116,

        SampleFormat = 0x0153,

        SamplesPerPixel = 0x0115,

        SMaxSampleValue = 0x0155,

        SMinSampleValue = 0x0154,

        SoftwareUsed = 0x0131,

        SRGBRenderingIntent = 0x0303,

        StripBytesCount = 0x0117,

        StripOffsets = 0x0111,

        SubfileType = 0x00FF,

        T4Option = 0x0124,

        T6Option = 0x0125,

        TargetPrinter = 0x0151,

        ThreshHolding = 0x0107,

        ThumbnailArtist = 0x5034,

        ThumbnailBitsPerSample = 0x5022,

        ThumbnailColorDepth = 0x5015,

        ThumbnailCompressedSize = 0x5019,

        ThumbnailCompression = 0x5023,

        ThumbnailCopyRight = 0x503B,

        ThumbnailData = 0x501B,

        ThumbnailDateTime = 0x5033,

        ThumbnailEquipMake = 0x5026,

        ThumbnailEquipModel = 0x5027,

        ThumbnailFormat = 0x5012,

        ThumbnailHeight = 0x5014,

        ThumbnailImageDescription = 0x5025,

        ThumbnailImageHeight = 0x5021,

        ThumbnailImageWidth = 0x5020,

        ThumbnailOrientation = 0x5029,

        ThumbnailPhotometricInterp = 0x5024,

        ThumbnailPlanarConfig = 0x502F,

        ThumbnailPlanes = 0x5016,

        ThumbnailPrimaryChromaticities = 0x5036,

        ThumbnailRawBytes = 0x5017,

        ThumbnailRefBlackWhite = 0x503A,

        ThumbnailResolutionUnit = 0x5030,

        ThumbnailResolutionX = 0x502D,

        ThumbnailResolutionY = 0x502E,

        ThumbnailRowsPerStrip = 0x502B,

        ThumbnailSamplesPerPixel = 0x502A,

        ThumbnailSize = 0x5018,

        ThumbnailSoftwareUsed = 0x5032,

        ThumbnailStripBytesCount = 0x502C,

        ThumbnailStripOffsets = 0x5028,

        ThumbnailTransferFunction = 0x5031,

        ThumbnailWhitePoint = 0x5035,

        ThumbnailWidth = 0x5013,

        ThumbnailYCbCrCoefficients = 0x5037,

        ThumbnailYCbCrPositioning = 0x5039,

        ThumbnailYCbCrSubsampling = 0x5038,

        TileByteCounts = 0x0145,

        TileLength = 0x0143,

        TileOffset = 0x0144,

        TileWidth = 0x0142,

        TransferFunction = 0x012D,

        TransferRange = 0x0156,

        WhitePoint = 0x013E,

        XPosition = 0x011E,

        XResolution = 0x011A,

        YCbCrCoefficients = 0x0211,

        YCbCrPositioning = 0x0213,

        YCbCrSubsampling = 0x0212,

        YPosition = 0x011F,

        YResolution = 0x011B
    }

    /// <summary>
    /// Retrieves information about an image
    /// </summary>
    /// <example>
    ///     <code language="c#">
    ///         Image image = Image.FromFile(path);
    ///         ImageInfo info = new ImageInfo(image);
    ///         foreach (KeyValuePair<PropertyTagId, KeyValuePair<PropertyTagType, Object>>property in info.ImageProps)
    ///         {
    ///             string parsed = property.Value.ToString();
    ///             parsed = parsed.Replace("[", "");
    ///             parsed = parsed.Replace("]", "");
    ///             string[] tokens = parsed.Split(',');
    ///             string value = tokens[1].Trim();
    ///             if (value != "Undefined Data" && value != "System.Object")
    ///             {
    ///                 Console.WriteLine(property.Key.ToString() + ": " + value);
    ///             }
    ///         }
    ///     </code>
    /// </example>
    public class ImageInfo
    {
        /// <summary>
        /// Stores image properties.
        /// </summary>
        private Dictionary<PropertyTagId, KeyValuePair<PropertyTagType, object>> imageProps =
            new Dictionary<PropertyTagId, KeyValuePair<PropertyTagType, object>>();

        /// <summary>
        /// Internally stores the image to inspect.
        /// </summary>
        private Image image;

        /// <summary>
        /// Initializes a new instance of the ImageInfo class.
        /// </summary>
        /// <param name="newImage">Image to inspect.</param>
        public ImageInfo(Image newImage)
        {
            this.image = newImage;

            this.BuildPropsHash();
        }

        /// <summary>
        /// Gets or sets image properties.
        /// </summary>
        public Dictionary<PropertyTagId, KeyValuePair<PropertyTagType, object>> ImageProps
        {
            get { return this.imageProps; }
            set { this.imageProps = value; }
        }

        /// <summary>
        /// Converts a number into an enumerable object.
        /// </summary>
        /// <typeparam name="T">Type to convert to.</typeparam>
        /// <param name="number">Number to convert.</param>
        /// <returns>Enumberable object.</returns>
        public T NumToEnum<T>(int number)
        {
            return (T)Enum.ToObject(typeof(T), number);
        }

        /// <summary>
        /// Builds the hash of image properties.
        /// </summary>
        private void BuildPropsHash()
        {
            foreach (PropertyItem property in this.image.PropertyItems)
            {
                object propValue = new object();

                switch ((PropertyTagType)property.Type)
                {
                    case PropertyTagType.ASCII:
                        ASCIIEncoding encoding = new ASCIIEncoding();
                        propValue = encoding.GetString(
                            property.Value, 0, property.Len - 1);
                        break;
                    case PropertyTagType.Int16:
                        propValue = BitConverter.ToInt16(
                            property.Value, 0);
                        break;
                    case PropertyTagType.SLONG:
                    case PropertyTagType.Int32:
                        propValue = BitConverter.ToInt32(
                            property.Value, 0);
                        break;
                    case PropertyTagType.SRational:
                    case PropertyTagType.Rational:
                        uint numberator =
                            BitConverter.ToUInt32(
                            property.Value, 0);
                        uint denominator =
                            BitConverter.ToUInt32(
                            property.Value, 4);
                        try
                        {
                            propValue = ((double)numberator
                                / (double)denominator).ToString();

                            if (propValue.ToString() == "NaN")
                            {
                                propValue = "0";
                            }
                        }
                        catch (DivideByZeroException)
                        {
                            propValue = "0";
                        }

                        break;
                    case PropertyTagType.Undefined:
                        propValue = "Undefined Data";
                        break;
                }

                try
                {
                    this.ImageProps.Add(
                        NumToEnum<PropertyTagId>(property.Id),
                        new KeyValuePair<PropertyTagType, object>(NumToEnum<PropertyTagType>(property.Type), propValue));
                }
                catch (Exception)
                {
                    continue;
                }
            }
        }
    }
}
